﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using ParallelRuntimeLibrary.Internal;

namespace ParallelRuntimeLibrary.Collections
{
    public class ConcurrentList<T>
    {
        internal sealed class NodeItem
        {
            public T Value;


            internal NodeItem()
            { }

            internal NodeItem(T item)
            {
                Value = item;
            }
        }

        private ListConcurrentModes mConcurrentMode;
        private int mCount;
        private NodeItem[] mArray;
        private ReaderWriterLockSlim mLock;
        private const int mDefaultCapacity = 128;
        private int mInitialSize;

        /*//////////////////////////////////////////////////////////////////////////////*/

        public ConcurrentList() : this(mDefaultCapacity, ListConcurrentModes.ReadWriteLock)
        {
        }

        public ConcurrentList(ListConcurrentModes concurrentMode)
            : this(mDefaultCapacity, concurrentMode)
        {
        }

        public ConcurrentList(int capacity, ListConcurrentModes concurrentMode)
        {
            mInitialSize = capacity;
            mConcurrentMode = concurrentMode;
            this.mArray = new NodeItem[capacity];

            if (concurrentMode == ListConcurrentModes.ReadWriteLock)
                mLock = new ReaderWriterLockSlim();
        }

        /*////////////////////////////////////////////////////////////////////////////////////////////////*/

        public int Capacity
        {
            get
            {
                return this.mArray.Length;
            }
        }

        public int Count
        {
            get
            {
                return mCount;
            }
        }

        public T this[int index]
        {
            get
            {
                return this.mArray[index].Value;
            }
            set
            {
                try
                {
                    mLock.EnterReadLock();
                    mArray[index].Value = value;
                }
                finally
                {
                    mLock.ExitReadLock();
                }
            }
        }

        /*//////////////////////////////////////////////////////////////////////////////*/

        public void Add(T item)
        {
            if (mConcurrentMode == ListConcurrentModes.None)
            {
                #region None

                if (mCount >= mArray.Length) Resize();
                mArray[mCount++] = new ConcurrentList<T>.NodeItem(item);

                #endregion
            }
            else if (mConcurrentMode == ListConcurrentModes.Lock)
            {
                #region Lock

                lock (mArray)
                {
                    if (mCount >= mArray.Length) Resize();
                    mArray[mCount++] = new ConcurrentList<T>.NodeItem(item);
                }

                #endregion
            }
            else
            {
                #region ReadWrite Lock

                do
                {
                    mLock.EnterReadLock();
                    int OldCount = mCount;

                    if ((OldCount < mArray.Length) && (OldCount == Interlocked.CompareExchange(ref mCount, (OldCount + 1), OldCount)))
                    {
                        mArray[OldCount] = new NodeItem(item);
                        mLock.ExitReadLock();
                        return;
                    }
                    else if (OldCount >= mArray.Length)
                    {
                        ReadWriteResize();
                        mLock.ExitReadLock();
                        continue;
                    }
                    else
                    {
                        OldCount = mCount;

                        if ((OldCount < mArray.Length) && (OldCount == Interlocked.CompareExchange(ref mCount, OldCount + 1, OldCount)))
                        {
                            mArray[OldCount] = new NodeItem(item);
                            mLock.ExitReadLock();
                            return;
                        }
                        else if (OldCount >= mArray.Length)
                        {
                            ReadWriteResize();
                            mLock.ExitReadLock();
                            continue;
                        }
                        else
                        {
                            mLock.ExitReadLock();
                            NativeMethod.SwitchToThread();
                        }
                    }

                } while (true);

                #endregion
            }
        }

        public void Insert(int index, T item)
        {
            if (mConcurrentMode == ListConcurrentModes.None)
            {
                #region None
                if (index > this.mCount) return;
                Array.Copy(this.mArray, index, this.mArray, index + 1, this.mCount - index);
                this.mArray[index].Value = item;
                this.mCount++;

                #endregion
            }
            else if (mConcurrentMode == ListConcurrentModes.Lock)
            {
                #region Lock

                lock (mArray)
                {
                    if (index > this.mCount) return;
                    Array.Copy(this.mArray, index, this.mArray, index + 1, this.mCount - index);
                    this.mArray[index].Value = item;
                    this.mCount++;
                }

                #endregion
            }
            else
            {
                #region ReadWrite Lock

                try
                {
                    mLock.EnterWriteLock();

                    if (index > this.mCount) return;
                    if (this.mCount >= this.mArray.Length)
                        ReadWriteResize();

                    Array.Copy(this.mArray, index, this.mArray, index + 1, this.mCount - index);
                    this.mArray[index].Value = item;
                    this.mCount++;
                }
                finally
                {
                    mLock.ExitWriteLock();
                }

                #endregion
            }
        }

        public T RemoveAt(int index)
        {
            if (index < 0 || index >= mCount) return default(T);
            T tmp;

            if (mConcurrentMode == ListConcurrentModes.None)
            {
                #region None

                tmp = mArray[index].Value;
                mCount--;
                if (index < mCount)
                    Array.Copy(this.mArray, index + 1, this.mArray, index, this.mCount - index);

                return tmp;

                #endregion
            }
            else if (mConcurrentMode == ListConcurrentModes.Lock)
            {
                #region Lock

                lock (mArray)
                {
                    tmp = mArray[index].Value;
                    mCount--;
                    if (index < mCount)
                        Array.Copy(this.mArray, index + 1, this.mArray, index, this.mCount - index);

                    return tmp;
                }

                #endregion
            }
            else
            {
                #region ReadWriteLock

                try
                {

                    mLock.EnterWriteLock();

                    tmp = mArray[index].Value;

                    mCount--;

                    if (index < mCount)
                        Array.Copy(this.mArray, index + 1, this.mArray, index, this.mCount - index);

                    return tmp;
                }
                finally
                {
                    mLock.ExitWriteLock();
                }

                #endregion
            }

        }

        public T RemoveAtEnd()
        {
            if (mConcurrentMode == ListConcurrentModes.None)
            {
                #region None

                mCount--;
                return mArray[mCount].Value;

                #endregion
            }
            else if (mConcurrentMode == ListConcurrentModes.Lock)
            {
                #region Lock

                lock (mArray)
                {
                    mCount--;
                    return mArray[mCount].Value;
                }

                #endregion
            }
            else
            {
                #region ReadWrite Lock

                do
                {
                    mLock.EnterReadLock();
                    int OldCount = mCount;

                    if (OldCount == Interlocked.CompareExchange(ref mCount, OldCount - 1, OldCount))
                    {
                        mLock.ExitReadLock();
                        return mArray[OldCount - 1].Value;
                    }
                    else
                    {
                        OldCount = mCount;
                        if (OldCount == Interlocked.CompareExchange(ref mCount, OldCount - 1, OldCount))
                        {
                            mLock.ExitReadLock();
                            return mArray[OldCount - 1].Value;
                        }
                        else
                        {
                            mLock.ExitReadLock();
                            NativeMethod.SwitchToThread();
                        }
                    }

                } while (true);

                #endregion
            }
        }

        private void Resize()
        {
            if (mCount < mArray.Length) return;

            NodeItem[] OldArray = mArray;
            NodeItem[] NewArray = new NodeItem[mArray.Length * 2];

            Array.Copy(mArray, NewArray, mArray.Length);

            Interlocked.CompareExchange(ref mArray, NewArray, OldArray);
        }

        private void ReadWriteResize()
        {
            if (mCount < mArray.Length) return;

            NodeItem[] OldArray = mArray;
            NodeItem[] NewArray = new NodeItem[mArray.Length * 2];

            Array.Copy(mArray, NewArray, mArray.Length);

            Interlocked.CompareExchange(ref mArray, NewArray, OldArray);
        }

        public void Clear()
        {
            try
            {
                mLock.EnterWriteLock();
                mArray = new NodeItem[mInitialSize];
            }
            finally
            {
                mLock.ExitWriteLock();
            }
        }
    }
}
